/*! \file
 * \brief Enter a brief description here.
 *
 * Enter a detailed description here. (optional)
 * \author Author (Ivo Wingelaar)
 * \date Date (2011)
 * \version Version (0.0.1)
 * \copyright Copyright (GNU LGPL v3)
 */

#ifndef __LYNX_MESH_MANIPULATOR_HPP_INCLUDED__
#define __LYNX_MESH_MANIPULATOR_HPP_INCLUDED__

#include "LynxMesh.hpp"

namespace Lynx
{

//! Interface for vertex manipulators.
/** You should derive your manipulator from this class if it shall be called for every vertex, getting as parameter just the vertex.
*/
class VertexManipulator
{
};

//! Vertex manipulator to set color to a fixed color for all vertices
class VertexColorSetManipulator : public VertexManipulator
{
public:
    VertexColorSetManipulator(Color color) :
        mColor(color)
    {
        //
    }

    void operator()(Vertex& vertex) const
    {
        vertex.mColor = mColor;
    }

private:
    Color mColor;
};

//! Vertex manipulator to set the alpha value of the vertex color to a fixed value
class VertexColorSetAlphaManipulator : public VertexManipulator
{
public:
    VertexColorSetAlphaManipulator(uint32_t alpha) :
        mAlpha(alpha)
    {
        //
    }

    void operator()(Vertex& vertex) const
    {
        vertex.mColor.set_alpha(mAlpha);
    }

private:
    uint32_t mAlpha;
};

//! Vertex manipulator which invertes the RGB values
class VertexColorInvertManipulator : public VertexManipulator
{
public:
    void operator()(Vertex& vertex) const
    {
        vertex.mColor.set_red(255-vertex.mColor.get_red());
        vertex.mColor.set_green(255-vertex.mColor.get_green());
        vertex.mColor.set_blue(255-vertex.mColor.get_blue());
    }
};

//! Vertex manipulator to set vertex color to one of two values depending on a given threshold
/** If average of the color value is >Threshold the High color is chosen, else Low. */
class VertexColorThresholdManipulator : public VertexManipulator
{
public:
    VertexColorThresholdManipulator(uint8_t threshold, Color low, Color high) :
        mThreshold(threshold),
        mLow(low),
        mHigh(high)
    {
        //
    }

    void operator()(Vertex& vertex) const
    {
        vertex.mColor = (vertex.mColor.get_average() > mThreshold) ? mHigh : mLow;
    }

private:
    uint8_t mThreshold;
    Color mLow;
    Color mHigh;
};

//! Vertex manipulator which adjusts the brightness by the given amount
/** A positive value increases brightness, a negative value darkens the colors. */
class VertexColorBrightnessManipulator : public VertexManipulator
{
public:
    VertexColorBrightnessManipulator(int32_t amount) :
        mAmount(amount)
    {
        //
    }

    void operator()(Vertex& vertex) const
    {
        vertex.mColor.set_red(Myth::Math::clamp(vertex.mColor.get_red()+mAmount, 0u, 255u));
        vertex.mColor.set_green(Myth::Math::clamp(vertex.mColor.get_green()+mAmount, 0u, 255u));
        vertex.mColor.set_blue(Myth::Math::clamp(vertex.mColor.get_blue()+mAmount, 0u, 255u));
    }

private:
    int32_t mAmount;
};

//! Vertex manipulator which adjusts the contrast by the given factor
/** Factors over 1 increase contrast, below 1 reduce it. */
class VertexColorContrastManipulator : public VertexManipulator
{
public:
    VertexColorContrastManipulator(float factor) :
        mFactor(factor)
    {
        //
    }

    void operator()(Vertex& vertex) const
    {
        vertex.mColor.set_red(Myth::Math::clamp((int32_t)Myth::Math::round((vertex.mColor.get_red()-128)*mFactor)+128, 0, 255));
        vertex.mColor.set_green(Myth::Math::clamp((int32_t)Myth::Math::round((vertex.mColor.get_green()-128)*mFactor)+128, 0, 255));
        vertex.mColor.set_blue(Myth::Math::clamp((int32_t)Myth::Math::round((vertex.mColor.get_blue()-128)*mFactor)+128, 0, 255));
    }

private:
    float mFactor;
};

//! Vertex manipulator which adjusts the contrast by the given factor and brightness by a signed amount.
/** Factors over 1 increase contrast, below 1 reduce it.
A positive amount increases brightness, a negative one darkens the colors. */
class VertexColorContrastBrightnessManipulator : public VertexManipulator
{
public:
    VertexColorContrastBrightnessManipulator(float factor, int32_t amount) :
        mFactor(factor),
        mAmount(amount+128)
    {
        //
    }

    void operator()(Vertex& vertex) const
    {
        vertex.mColor.set_red(Myth::Math::clamp((int32_t)Myth::Math::round((vertex.mColor.get_red()-128)*mFactor)+mAmount, 0, 255));
        vertex.mColor.set_green(Myth::Math::clamp((int32_t)Myth::Math::round((vertex.mColor.get_green()-128)*mFactor)+mAmount, 0, 255));
        vertex.mColor.set_blue(Myth::Math::clamp((int32_t)Myth::Math::round((vertex.mColor.get_blue()-128)*mFactor)+mAmount, 0, 255));
    }

private:
    float mFactor;
    int32_t mAmount;
};

//! Vertex manipulator which adjusts the brightness by a gamma operation
/** A value over one increases brightness, one below darkens the colors. */
class VertexColorGammaManipulator : public VertexManipulator
{
public:
    VertexColorGammaManipulator(float gamma) :
        mGamma(1.f)
    {
        if(gamma != 0.f)
            mGamma = 1.f/gamma;
    }

    void operator()(Vertex& vertex) const
    {
        vertex.mColor.set_red(Myth::Math::clamp((int32_t)Myth::Math::round(powf((float)(vertex.mColor.get_red()),mGamma)), 0, 255));
        vertex.mColor.set_green(Myth::Math::clamp((int32_t)Myth::Math::round(powf((float)(vertex.mColor.get_green()),mGamma)), 0, 255));
        vertex.mColor.set_blue(Myth::Math::clamp((int32_t)Myth::Math::round(powf((float)(vertex.mColor.get_blue()),mGamma)), 0, 255));
    }

private:
    float mGamma;
};

//! Vertex manipulator which scales the color values
/** Can e.g be used for white balance, factor would be 255.f/brightest color. */
class VertexColorScaleManipulator : public VertexManipulator
{
public:
    VertexColorScaleManipulator(float factor) :
        mFactor(factor)
    {
        //
    }

    void operator()(Vertex& vertex) const
    {
        vertex.mColor.set_red(Myth::Math::clamp((int32_t)Myth::Math::round(vertex.mColor.get_red()*mFactor), 0, 255));
        vertex.mColor.set_green(Myth::Math::clamp((int32_t)Myth::Math::round(vertex.mColor.get_green()*mFactor), 0, 255));
        vertex.mColor.set_blue(Myth::Math::clamp((int32_t)Myth::Math::round(vertex.mColor.get_blue()*mFactor), 0, 255));
    }

private:
    float mFactor;
};

//! Vertex manipulator which desaturates the color values
/** Uses the lightness value of the color. */
class VertexColorDesaturateToLightnessManipulator : public VertexManipulator
{
public:
    void operator()(Vertex& vertex) const
    {
        vertex.mColor = Myth::Math::round(vertex.mColor.get_lightness());
    }
};

//! Vertex manipulator which desaturates the color values
/** Uses the average value of the color. */
class VertexColorDesaturateToAverageManipulator : public VertexManipulator
{
public:
    void operator()(Vertex& vertex) const
    {
        vertex.mColor = vertex.mColor.get_average();
    }
};

//! Vertex manipulator which desaturates the color values
/** Uses the luminance value of the color. */
class VertexColorDesaturateToLuminanceManipulator : public VertexManipulator
{
public:
    void operator()(Vertex& vertex) const
    {
        vertex.mColor = Myth::Math::round(vertex.mColor.get_luminance());
    }
};

//! Vertex manipulator which interpolates the color values
/** Uses linear interpolation. */
class VertexColorInterpolateLinearManipulator : public VertexManipulator
{
public:
    VertexColorInterpolateLinearManipulator(Color color, float factor) :
        mColor(color),
        mFactor(factor)
    {
        //
    }

    void operator()(Vertex& vertex) const
    {
        vertex.mColor = vertex.mColor.get_interpolated(mColor, mFactor);
    }

private:
    Color mColor;
    float mFactor;
};

//! Vertex manipulator which interpolates the color values
/** Uses linear interpolation. */
class VertexColorInterpolateQuadraticManipulator : public VertexManipulator
{
public:
    VertexColorInterpolateQuadraticManipulator(Color color1, Color color2, float factor) :
        mColor1(color1),
        mColor2(color2),
        mFactor(factor)
    {
        //
    }

    void operator()(Vertex& vertex) const
    {
        vertex.mColor = vertex.mColor.get_interpolated_quadratic(mColor1, mColor2, mFactor);
    }

private:
    Color mColor1;
    Color mColor2;
    float mFactor;
};

//! Vertex manipulator which scales the position of the vertex
class VertexPositionScaleManipulator : public VertexManipulator
{
public:
    VertexPositionScaleManipulator(const Myth::Math::Vector3f factor) :
        mFactor(factor)
    {
        //
    }

    template <typename VType>
    void operator()(VType& vertex) const
    {
        vertex.mPos *= mFactor;
    }

private:
    Myth::Math::Vector3f mFactor;
};

//! Vertex manipulator which scales the position of the vertex along the normals
/** This can look more pleasing than the usual Scale operator, but
depends on the mesh geometry.
*/
class VertexPositionScaleAlongNormalsManipulator : public VertexManipulator
{
public:
    VertexPositionScaleAlongNormalsManipulator(const Myth::Math::Vector3f factor) :
        mFactor(factor)
    {
        //
    }

    template <typename VType>
    void operator()(VType& vertex) const
    {
        vertex.mPos += vertex.mNormal*mFactor;
    }

private:
    Myth::Math::Vector3f mFactor;
};

//! Vertex manipulator which transforms the position of the vertex
class VertexPositionTransformManipulator : public VertexManipulator
{
public:
    VertexPositionTransformManipulator(const Myth::Math::Matrix4 m) :
        mTransformation(m)
    {
        //
    }

    template <typename VType>
    void operator()(VType& vertex) const
    {
        mTransformation.transformVect(vertex.mPos);
    }

private:
    Myth::Math::Matrix4 mTransformation;
};

//! Vertex manipulator which scales the TCoords of the vertex
class VertexTCoordsScaleManipulator : public VertexManipulator
{
public:
    VertexTCoordsScaleManipulator(const Myth::Math::Vector2f factor, uint32_t uvSet = 1) :
        mFactor(factor),
        mUVSet(uvSet)
    {
        //
    }

    void operator()(Vertex2TCoords& vertex) const
    {
        if(1 == mUVSet)
            vertex.mTCoords *= mFactor;
        else if(2 == mUVSet)
            vertex.mTCoords2 *= mFactor;
    }

    template <typename VType>
    void operator()(VType& vertex) const
    {
        if(1 == mUVSet)
            vertex.mTCoords *= mFactor;
    }

private:
    Myth::Math::Vector2f mFactor;
    uint32_t mUVSet;
};

//! An interface for easy manipulation of meshes.
/** Scale, set alpha value, flip surfaces, and so on. This exists for
fixing problems with wrong imported or exported meshes quickly after
loading. It is not intended for doing mesh modifications and/or
animations during runtime.
*/
class MeshManipulator : public virtual Myth::ReferenceCounter
{
public:

    //! Flips the direction of surfaces.
    /** Changes backfacing triangles to frontfacing
    triangles and vice versa.
    \param mesh Mesh on which the operation is performed. */
    virtual void flip_surfaces(Mesh* mesh) const = 0;

    //! Sets the alpha vertex color value of the whole mesh to a new value.
    /** \param mesh Mesh on which the operation is performed.
    \param alpha New alpha value. Must be a value between 0 and 255. */
    void set_vertex_color_alpha(Mesh* mesh, int32_t alpha) const
    {
        apply(VertexColorSetAlphaManipulator(alpha), mesh);
    }

    //! Sets the colors of all vertices to one color
    /** \param mesh Mesh on which the operation is performed.
    \param color New color. */
    void set_vertex_colors(Mesh* mesh, Color color) const
    {
        apply(VertexColorSetManipulator(color), mesh);
    }

    //! Recalculates all normals of the mesh.
    /** \param mesh: Mesh on which the operation is performed.
    \param smooth: If the normals shall be smoothed.
    \param angleWeighted: If the normals shall be smoothed in relation to their angles. More expensive, but also higher precision. */
    virtual void recalculate_normals(Mesh* mesh, bool smooth = false, bool angleWeighted = false) const = 0;

    //! Recalculates all normals of the mesh buffer.
    /** \param buffer: Mesh buffer on which the operation is performed.
    \param smooth: If the normals shall be smoothed.
    \param angleWeighted: If the normals shall be smoothed in relation to their angles. More expensive, but also higher precision. */
    virtual void recalculate_normals(MeshBuffer* buffer, bool smooth = false, bool angleWeighted = false) const = 0;

    //! Recalculates tangents, requires a tangent mesh
    /** \param mesh Mesh on which the operation is performed.
    \param recalculate_normals If the normals shall be recalculated, otherwise original normals of the mesh are used unchanged.
    \param smooth If the normals shall be smoothed.
    \param angleWeighted If the normals shall be smoothed in relation to their angles. More expensive, but also higher precision.
    */
    virtual void recalculate_tangents(Mesh* mesh, bool recalculate_normals=false, bool smooth=false, bool angleWeighted=false) const=0;

    //! Scales the actual mesh, not a scene node.
    /** \param mesh Mesh on which the operation is performed.
    \param factor Scale factor for each axis. */
    void scale(Mesh* mesh, const Myth::Math::Vector3f factor) const
    {
        apply(VertexPositionScaleManipulator(factor), mesh, true);
    }

    //! Scales the actual meshbuffer, not a scene node.
    /** \param buffer Meshbuffer on which the operation is performed.
    \param factor Scale factor for each axis. */
    void scale(MeshBuffer* buffer, const Myth::Math::Vector3f factor) const
    {
        apply(VertexPositionScaleManipulator(factor), buffer, true);
    }

    //! Scale the texture coords of a mesh.
    /** \param mesh Mesh on which the operation is performed.
    \param factor Vector which defines the scale for each axis.
    \param level Number of texture coord, starting from 1. Support for level 2 exists for LightMap buffers. */
    void scale_texture_coords(Mesh* mesh, const Myth::Math::Vector2f factor, uint32_t level=1) const
    {
        apply(VertexTCoordsScaleManipulator(factor, level), mesh);
    }

    //! Scale the texture coords of a meshbuffer.
    /** \param buffer Meshbuffer on which the operation is performed.
    \param factor Vector which defines the scale for each axis.
    \param level Number of texture coord, starting from 1. Support for level 2 exists for LightMap buffers. */
    void scale_texture_coords(MeshBuffer* buffer, const Myth::Math::Vector2f factor, uint32_t level=1) const
    {
        apply(VertexTCoordsScaleManipulator(factor, level), buffer);
    }

    //! Applies a transformation to a mesh
    /** \param mesh Mesh on which the operation is performed.
    \param m transformation matrix. */
    void transform(Mesh* mesh, const Myth::Math::Matrix4 m) const
    {
        apply(VertexPositionTransformManipulator(m), mesh, true);
    }

    //! Applies a transformation to a meshbuffer
    /** \param buffer Meshbuffer on which the operation is performed.
    \param m transformation matrix. */
    void transform(MeshBuffer* buffer, const Myth::Math::Matrix4 m) const
    {
        apply(VertexPositionTransformManipulator(m), buffer, true);
    }

    //! Clones a static IMesh into a modifiable SMesh.
    /** All meshbuffers in the returned SMesh
    are of type SMeshBuffer or SMeshBufferLightMap.
    \param mesh Mesh to copy.
    \return Cloned mesh. If you no longer need the
    cloned mesh, you should call SMesh::drop(). See
    IReferenceCounted::drop() for more information. */
    virtual Mesh* create_mesh_copy(Mesh* mesh) const = 0;

    //! Creates a planar texture mapping on the mesh
    /** \param mesh: Mesh on which the operation is performed.
    \param resolution: resolution of the planar mapping. This is
    the value specifying which is the relation between world space
    and texture coordinate space. */
    virtual void make_planar_texture_mapping(Mesh* mesh, float resolution=0.001f) const =0;

    //! Creates a planar texture mapping on the meshbuffer
    /** \param meshbuffer: Buffer on which the operation is performed.
    \param resolution: resolution of the planar mapping. This is
    the value specifying which is the relation between world space
    and texture coordinate space. */
    virtual void make_planar_texture_mapping(MeshBuffer* meshbuffer, float resolution=0.001f) const =0;

    //! Creates a planar texture mapping on the meshbuffer
    /** This method is currently implemented towards the LWO planar mapping. A more general biasing might be required.
    \param buffer Buffer on which the operation is performed.
    \param resolutionS Resolution of the planar mapping in horizontal direction. This is the ratio between object space and texture space.
    \param resolutionT Resolution of the planar mapping in vertical direction. This is the ratio between object space and texture space.
    \param axis The axis along which the texture is projected. The allowed values are 0 (X), 1(Y), and 2(Z).
    \param offset Vector added to the vertex positions (in object coordinates).
    */
    virtual void make_planar_texture_mapping(MeshBuffer* buffer, float resolutionS, float resolutionT, uint8_t axis, const Myth::Math::Vector3f offset) const =0;

    //! Creates a copy of the mesh, which will only consist of S3DVertexTangents vertices.
    /** This is useful if you want to draw tangent space normal
    mapped geometry because it calculates the tangent and binormal
    data which is needed there.
    \param mesh Input mesh
    \param recalculate_normals The normals are recalculated if set,
    otherwise the original ones are kept. Note that keeping the
    normals may introduce inaccurate tangents if the normals are
    very different to those calculated from the faces.
    \param smooth The normals/tangents are smoothed across the
    meshbuffer's faces if this flag is set.
    \param angleWeighted Improved smoothing calculation used
    \param recalculate_tangents Whether are actually calculated, or just the mesh with proper type is created.
    \return Mesh consisting only of S3DVertexTangents vertices. If
    you no longer need the cloned mesh, you should call
    IMesh::drop(). See IReferenceCounted::drop() for more
    information. */
    virtual Mesh* create_mesh_with_tangents(Mesh* mesh, bool recalculate_normals=false, bool smooth=false, bool angleWeighted=false, bool recalculate_tangents=true) const = 0;

    //! Creates a copy of the mesh, which will only consist of S3DVertex2TCoord vertices.
    /** \param mesh Input mesh
    \return Mesh consisting only of S3DVertex2TCoord vertices. If
    you no longer need the cloned mesh, you should call
    IMesh::drop(). See IReferenceCounted::drop() for more
    information. */
    virtual Mesh* create_mesh_with_2_texture_coords(Mesh* mesh) const = 0;

    //! Creates a copy of the mesh, which will only consist of S3DVertex vertices.
    /** \param mesh Input mesh
    \return Mesh consisting only of S3DVertex vertices. If
    you no longer need the cloned mesh, you should call
    IMesh::drop(). See IReferenceCounted::drop() for more
    information. */
    virtual Mesh* create_mesh_with_1_texture_coords(Mesh* mesh) const = 0;

    //! Creates a copy of a mesh with all vertices unwelded
    /** \param mesh Input mesh
    \return Mesh consisting only of unique faces. All vertices
    which were previously shared are now duplicated. If you no
    longer need the cloned mesh, you should call IMesh::drop(). See
    IReferenceCounted::drop() for more information. */
    virtual Mesh* create_mesh_with_unique_primitives(Mesh* mesh) const = 0;

    //! Creates a copy of a mesh with vertices welded
    /** \param mesh Input mesh
    \param tolerance The threshold for vertex comparisons.
    \return Mesh without redundant vertices. If you no longer need
    the cloned mesh, you should call IMesh::drop(). See
    IReferenceCounted::drop() for more information. */
    virtual Mesh* create_mesh_with_welded_vertices(Mesh* mesh, float tolerance = Myth::Math::ROUNDING_ERROR_32) const = 0;

    //! Get amount of polygons in mesh.
    /** \param mesh Input mesh
    \return Number of polygons in mesh. */
    virtual int32_t get_poly_count(Mesh* mesh) const = 0;

    //! Get amount of polygons in mesh.
    /** \param mesh Input mesh
    \return Number of polygons in mesh. */
    virtual int32_t get_poly_count(AnimatedMesh* mesh) const = 0;

    //! Create a new AnimatedMesh and adds the mesh to it
    /** \param mesh Input mesh
    \param type The type of the animated mesh to create.
    \return Newly created animated mesh with mesh as its only
    content. When you don't need the animated mesh anymore, you
    should call IAnimatedMesh::drop(). See
    IReferenceCounted::drop() for more information. */
    virtual AnimatedMesh* create_animated_mesh(Mesh* mesh) const = 0;

    //! Apply a manipulator on the Meshbuffer
    /** \param func A functor defining the mesh manipulation.
    \param buffer The Meshbuffer to apply the manipulator to.
    \param boundingBoxUpdate Specifies if the bounding box should be updated during manipulation.
    \return True if the functor was successfully applied, else false. */
    template <typename Functor>
    bool apply(const Functor& func, MeshBuffer* buffer, bool boundingBoxUpdate=false) const
    {
        return apply_(func, buffer, boundingBoxUpdate, func);
    }

    //! Apply a manipulator on the Mesh
    /** \param func A functor defining the mesh manipulation.
    \param mesh The Mesh to apply the manipulator to.
    \param boundingBoxUpdate Specifies if the bounding box should be updated during manipulation.
    \return True if the functor was successfully applied, else false. */
    template <typename Functor>
    bool apply(const Functor& func, Mesh* mesh, bool boundingBoxUpdate=false) const
    {
        if(!mesh)
            return true;
        bool result = true;
        Myth::Math::BoundingBox3f bufferbox;
        for(uint32_t i=0; i<mesh->get_mesh_buffer_count(); ++i)
        {
            result &= apply(func, mesh->get_mesh_buffer(i), boundingBoxUpdate);
            if(boundingBoxUpdate)
            {
                if(0==i)
                    bufferbox.reset(mesh->get_mesh_buffer(i)->get_bounding_box());
                else
                    bufferbox.addInternalBox(mesh->get_mesh_buffer(i)->get_bounding_box());
            }
        }
        if(boundingBoxUpdate)
            mesh->set_bounding_box(bufferbox);
        return result;
    }

protected:
    //! Apply a manipulator based on the type of the functor
    /** \param func A functor defining the mesh manipulation.
    \param buffer The Meshbuffer to apply the manipulator to.
    \param boundingBoxUpdate Specifies if the bounding box should be updated during manipulation.
    \param typeTest Unused parameter, which handles the proper call selection based on the type of the Functor which is passed in two times.
    \return True if the functor was successfully applied, else false. */
    template <typename Functor>
    bool apply_(const Functor& func, MeshBuffer* buffer, bool boundingBoxUpdate, const VertexManipulator& typeTest) const
    {
        if(!buffer)
            return true;

        Myth::Math::BoundingBox3f bufferbox;
        for(uint32_t i=0; i<buffer->get_vertex_count(); ++i)
        {
            switch(buffer->get_vertex_type())
            {
            case EVT_STANDARD:
            {
                Vertex* verts = (Vertex*)buffer->get_vertices();
                func(verts[i]);
            }
            break;
            case EVT_2TCOORDS:
            {
                Vertex2TCoords* verts = (Vertex2TCoords*)buffer->get_vertices();
                func(verts[i]);
            }
            break;
            case EVT_TANGENTS:
            {
                VertexTangents* verts = (VertexTangents*)buffer->get_vertices();
                func(verts[i]);
            }
            break;
            }
            if(boundingBoxUpdate)
            {
                if(0==i)
                    bufferbox.reset(buffer->get_position(0));
                else
                    bufferbox.addInternalPoint(buffer->get_position(i));
            }
        }
        if(boundingBoxUpdate)
            buffer->set_bounding_box(bufferbox);
        return true;
    }
};

} // namespace Lynx

#endif // __LYNX_MESH_MANIPULATOR_HPP_INCLUDED__

